﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Model;

namespace Core.Parsers
{
    class Request
    {
        private MemoryStream ms;
        private BinaryReader sr;
        private Model.Elements.HttpRequestPacket packet;
        private List<Model.Elements.HttpHeader> lstHeaders;

        public bool error = false;
        public string remoteHost = string.Empty;
        public int remotePort = 80;

        public Request()
        {
            lstHeaders = new List<Model.Elements.HttpHeader>();
        }

        public static bool IsValidPacket(byte[] packet)
        {
            Request r = new Request();
            r.Parse(packet, false);
            return !r.error;
        }

        public void Parse(byte[] packet, bool ssl)
        {

            string method = string.Empty;
            string uri = string.Empty;
            string version = string.Empty;

            ms = new MemoryStream(packet);
            sr = new BinaryReader(ms);

            if (ms.Length == 0)
            {
                error = true;
                return;
            }


            ProcessFirstLine(out method, out uri, out version, out error);
            if (error)
                return;

            if (method.ToLower() == "connect")
            {
                if (!Configuration.Configuration.openProxy || uri.Split(':').Length != 2)
                {
                    error = true;
                    return;
                }

                try
                {
                    remoteHost = uri.Split(':')[0];
                    remotePort = int.Parse(uri.Split(':')[1]);
                }
                catch
                {
                    error = true;
                    return;
                }
            }

            // open proxy parse. When URI is a remote url...
            /*
            if (Configuration.Configuration.openProxy && method.ToLower() == "connect")
            {
                string forwardHost = uri.Split(':')[0];
                string forwardPort = uri.Split(':')[1];


            }
            else
                ProcessHeaders(out lstHeaders, true, out error);
            */

            if (uri.ToLower().StartsWith("http://") || uri.ToLower().StartsWith("https://"))
            {
                if (!Configuration.Configuration.openProxy)
                {
                    error = true;
                    return;
                }

                Uri u = new Uri(uri);
                uri = u.PathAndQuery;
                ProcessHeaders(out lstHeaders, false, out error);
                lstHeaders.Add(new Model.Elements.HttpHeader("Host", u.Host));
                remoteHost = u.Host;
                remotePort = u.Port;
            }
            else
            {
                ProcessHeaders(out lstHeaders, true, out error);

                if (error)
                    return;

                error = ErrorInHostHeader();
                if (error)
                    return;
            }

            bool thereIsData = false;
            int contentLength = 0;
            for (int iHeader = 0; iHeader < lstHeaders.Count; iHeader++)
            {
                if (lstHeaders[iHeader].name == "Content-Length")
                {
                    thereIsData = true;
                    try
                    {
                        contentLength = int.Parse(lstHeaders[iHeader].value);
                    }
                    catch
                    {
                        // Invalid content length
                        return;
                    }
                    break;
                }
            }

            byte[] data = null;

            // If there is content-lenght..
            if (thereIsData)
            {
                data = ProcessData(contentLength, out error);
                if (error)
                {
                    // content lenght with diferent length than data length
                    return;
                }
            }
            else
            {
                // If not the packet must end with 0x0d 0x0a 0x0d 0x0a
                if (packet[packet.Length - 0x04] != 0x0d ||
                    packet[packet.Length - 0x03] != 0x0a ||
                    packet[packet.Length - 0x02] != 0x0d ||
                    packet[packet.Length - 0x01] != 0x0a)
                {
                    error = true;
                    return;
                }
            }

            this.packet = ReassemblePacket(method, uri, version, lstHeaders, data, ssl);
        }

        private bool ErrorInHostHeader()
        {
            bool HostHeaderFound = false;
            for (int i = 0; i < lstHeaders.Count; i++)
            {
                if (lstHeaders[i].name == "Host")
                {
                    if (lstHeaders[i].value.Split(':').Length > 1)
                    {
                        remoteHost = lstHeaders[i].value.Split(':')[0];
                        try
                        {
                            remotePort = int.Parse(lstHeaders[i].value.Split(':')[1]);
                        }
                        catch
                        {
                            return true;
                        }
                    }
                    else
                    {
                        remoteHost = lstHeaders[i].value;
                        remotePort = 80;
                    }

                    HostHeaderFound = true;
                    break;
                }
            }
            if (!HostHeaderFound)
            {
                return true;
            }

            return false;
        }

        public Model.Elements.HttpRequestPacket GetPacket()
        {
            return this.packet;
        }

        private void ProcessFirstLine(out string method, out string uri, out string version, out bool error)
        {
            //METHOD URI VERSION/X

            /// OUT PARAMETERS
            error = false;
            method = string.Empty;
            uri = string.Empty;
            version = string.Empty;

            ms.Seek(0, SeekOrigin.Begin);

            string line = sr.ReadLine();

            string[] splitedLine = line.Split(' ');
            if (splitedLine.Length != 3)
            {
                error = true;
                return;
            }

            method = splitedLine[0];
            uri = splitedLine[1];
            version = splitedLine[2];
        }

        private void ProcessHeaders(out List<Model.Elements.HttpHeader> lstHeaders, bool processHostHeader, out bool error)
        {
            error = false;
            lstHeaders = new List<Model.Elements.HttpHeader>();

            string line = string.Empty;
            while (!string.IsNullOrEmpty(line = sr.ReadLine()))
            {
                if (!line.Contains(':'))
                {
                    error = true;
                    return;
                }

                string nameHeader = line.Split(':')[0];
                string valueHeader = line.Substring(nameHeader.Length + 1).Trim();

                Model.Elements.HttpHeader header = null;

                if (nameHeader.ToLower() == "host")
                {
                    if (processHostHeader)
                        header = new Model.Elements.HttpHeader(nameHeader, valueHeader);
                    else
                        continue;
                }
                else
                    header = new Model.Elements.HttpHeader(nameHeader, valueHeader);
                lstHeaders.Add(header);
            }

        }

        private byte[] ProcessData(int bytesToRead, out bool error)
        {
            error = false;
            byte[] data = new byte[bytesToRead];
            int numBytesReaded = sr.Read(data, 0, data.Length);
            if (numBytesReaded != bytesToRead)
            {
                error = true;
                return null;
            }

            return data;
        }

        private Model.Elements.HttpRequestPacket ReassemblePacket(string method, string uri, string version, List<Model.Elements.HttpHeader> lstHeaders, byte[] data, bool ssl)
        {
            Model.Elements.HttpRequestPacket packet = new Model.Elements.HttpRequestPacket(method, uri, version, ssl);
            foreach (Model.Elements.HttpHeader header in lstHeaders)
            {
                packet.AddHeader(header);
            }
            packet.data = data;
            return packet;
        }

    }
}
